//
//  PMShape.m
//  PezMonkey
//
//  Created by Spence DiNicolantonio on 10/29/09.
//  Copyright 2009 Necrosoft. All rights reserved.
//

/**
 * A collision shape. PMShape objects are used within a PMSpace to detect and 
 * control collisions of rigid bodies. PMShape objects are tied to a PMBody object
 * that it is meant to represent. The physical result of any interaction with other
 * PMShapes in a given PMSpace will in turn be applied to the PMShape's referenced
 * PMBody object. PMShape objects maintain surface velocity, friction, and elasticity  
 * properties that influence the affect on and by other PMShapes in a PMSpace. 
 * 
 * This class should never be instantiated directly, rather, PMShapes should be 
 * instantiated using the PMCircleShape, PMSegmentShape, and PMPolygonShape subclasses. 
 * 
 * In terms of processing, PMCircleShapes are the most efficient, and PMPolygonShapes
 * are the least efficient. Note that at this time, PMSegmentShapes cannot collide 
 * with other PMSegmentShapes. They are intended for use as a static shape.
 * 
 * When creating a PMShape object for static use, the static body singleton should 
 * be used. This can be attained using the static staticBody method of the PMBody
 * class.
 * 
 * PMShape objects can set a single direction in which other shapes can pass through
 * them without generating collisions. This direction is set via the passThrough
 * member variable. By default, passThrough is set to PMVectZero, specifying that 
 * collisions will occur regardless of direction. This can be useful for creating 
 * platforms that can be passed (or jumped through) from below. The pass-through 
 * is disabled by default, due to the amount of overhead associated with it. The
 * pass-through feature must be enabled explicitly in the PMSpace controlling the 
 * simulation.
 * 
 */

#import "PMShape.h"
#import "PMBody.h"
#import "PMShape.h"
#import "PMConstraint.h"


@implementation PMShape

@synthesize body;
@synthesize passThrough;
@synthesize data;


#pragma mark -
#pragma mark Init

- (id)init {
	if (![super init])
		return nil;
	
	// cannot instantiate this class directly (abstract class)
	if (self.class == PMShape.class) {
		[NSException raise:@"Abstract class instantiation" 
					format:@"Cannot instantiate abstract PMShape class"];
		return nil;
	}
	
	// initialize members
	self.body = nil;
	self.passThrough = PMVectZero;
	
	return self;
}


#pragma mark -
#pragma mark Dealloc

/**
 * Releases member objects and frees memory used for cpShape before deallocating.
 */
- (void)dealloc {
	[self.data release];
	[self.body release];
	cpShapeFree(shape);
	[super dealloc];
}


#pragma mark -
#pragma mark Getters

/**
 * Returns the receiver's cpShape reference. This is not intended for external use.
 */
- (cpShape *)cpShape {
	return shape;
}

/**
 * Returns the receiver's shape type.
 */
- (PMShapeType)type {
	return (PMShapeType) shape->klass->type;
}

/**
 * Returns the receiver's bounding box.
 */
- (PMBoundingBox)boundingBox {
	cpShapeCacheBB(shape);
	return shape->bb;
}

/**
 * Returns the receiver's elasticity.
 */
- (PMFloat)elasticity {
	return shape->e;
}

/**
 * Returns the receiver's coefficient of friction.
 */
- (PMFloat)friction {
	return shape->u;
}

/**
 * Returns the receiver's surface velocity vector.
 */
- (PMVect)surfaceVelocity {
	return (PMVect) shape->surface_v;
}

/**
 * Returns the receiver's collision Type. This value can be used to sort out custom 
 * collision functionality.
 */
- (PMCollisionType)collisionType {
	return shape->collision_type;
}

/**
 * Returns the receiver's collision group. Shapes in the same non-zero group do 
 * not generate collisions. This can be useful when creating an object out of 
 * multiple PMShapes that you don't want to self collide.
 */
- (PMCollisionGroup)collisionGroup {
	return shape->group;
}

/**
 * Returns the receiver's collision layer mask. This bit-mask denotes all of
 * layers on which the PMShape can collide. PMShapes only collide with other 
 * PMShapes sharing a common collision layer. By default, PMShapes occupy all 32 
 * layers.
 */
- (PMLayerMask)layers {
	return shape->layers;
}

/**
 * Returns the receiver's unique shape ID. 
 */
- (NSUInteger)hashID {
	return shape->hashid;
}

/**
 * Returns YES if the receiver is a sensor shape; a shape that invokes callbacks,
 * but does not generate collisions.
 */
- (BOOL)isSensor {
	return (BOOL) shape->sensor;
}


#pragma mark -
#pragma mark Setters

/**
 * Sets the receiver's elasticity to a given value. The given value must be in 
 * the range [0.0, 1.0], excluding 1.0. If not, it will be clamped to an acceptable 
 * value.
 */
- (void)setElasticity:(PMFloat)elasticity {
	shape->e = PMFloatClamp(elasticity, 0.0, PMFloatNext(1.0, 0.0));
}

/**
 * Sets the receiver's coefficient of friction to a given value. If the given value
 * is less than 0, The receiver's coefficient of friction will be set to 0. 
 */
- (void)setFriction:(PMFloat)friction {
	shape->u = (friction < 0)? 0 : friction;
}

/**
 * Sets the receiver's surface velocity to a given vector.
 */
- (void)setSurfaceVelocity:(PMVect)surfaceVelocity {
	shape->surface_v = surfaceVelocity;
}

/**
 * Sets the receiver's collision type to a given value.
 */
- (void)setCollisionType:(PMCollisionType)collisionType {
	shape->collision_type = collisionType;
}

/**
 * Sets the receiver's collision group to a given value.
 */
- (void)setCollisionGroup:(PMCollisionGroup)collisionGroup {
	shape->group = collisionGroup;
}

/**
 * Sets the receiver's collision Layer mask to the given PMLayerMask.
 */
- (void)setLayers:(PMLayerMask)layers {
	shape->layers = layers;
}

/**
 * Controls whether the receiver is a sensor shape.
 */
- (void)setSensor:(BOOL)flag {
	shape->sensor = (int) flag;
}


#pragma mark -
#pragma mark Other Methods

/**
 * Determines whether a given point lies within the receiver, disregarding collision
 * groups and layers.
 */
- (BOOL)containsPoint:(PMVect)point {
	return cpShapePointQuery(shape, point);
}

/**
 * Determines whether a given point lies within the receiver, taking into account
 * the receiver's layer mask and collision group with respect to those given.
 */
- (BOOL)containsPoint:(PMVect)point layers:(PMLayerMask)layers group:(PMCollisionGroup)group {
	// point in same group?
	if (group == [self collisionGroup] && group != 0)
		return NO;
	
	// point in different layers?
	if (!(layers & [self layers]))
		return NO;
	
	// point in shape?
	return cpShapePointQuery(shape, point);
}

/**
 * Determines whether a given segment collides with the receiver. The given 
 * PMSegmentCollisionInfo structure will be populated with all needed information 
 * about the collision. If no collision occurs, the structure will be set to 
 * PMSegmentCollisionInfoNone.
 */
- (BOOL)intersectsSegment:(PMSegment)segment layers:(PMLayerMask)layers group:(PMCollisionGroup)group info:(PMSegmentCollisionInfo *)info {
	// point in same group?
	if (group == [self collisionGroup] && group != 0) {
		*info = PMSegmentCollisionInfoNone;
		return NO;
	}
	
	// point in different layers?
	if (!(layers & [self layers])) {
		*info = PMSegmentCollisionInfoNone;
		return NO;
	}
	
	// run segment query
	cpSegmentQueryInfo queryInfo = {NULL, 0.0, cpvzero};
	BOOL result = cpShapeSegmentQuery(shape, segment.start, segment.end, &queryInfo);
	
	// no collision?
	if (!result) {
		*info = PMSegmentCollisionInfoNone;
		return NO;
	}
	
	// no collision info structure given?
	if (!info)
		return YES;
	
	// set collision info
	info->segment = segment;
	info->shape = self;
	info->intersect = queryInfo.t;
	info->normal = queryInfo.n;
	return YES;
}

/**
 * Resets the internal counter used to set unique hash IDs for all created collision
 * shapes. This method should be called before populating a PMSpace with PMShape
 * objects.
 */
+ (void)resetShapeIDCounter {
	cpResetShapeIdCounter();
}

@end


#pragma mark -
#pragma mark Functions

/**
 * Calculates the location, in world coordinates, where the segment first intersected
 * the shape.
 */
PMVect PMSegmentCollisionHitPoint(PMSegmentCollisionInfo info) {
	return PMVectLerp(info.segment.start, info.segment.end, info.intersect);
}
	
/**
 * Calculates the distance along the segment at which the segment first intersected
 * the shape.
 */
PMFloat PMSegmentCollisionHitDistance(PMSegmentCollisionInfo info) {
	return PMVectDist(info.segment.start, info.segment.end) * info.intersect;
}
